package com.mimo.common.logic.dto.msg;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import com.fasterxml.jackson.databind.DatabindContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver;
import com.fasterxml.jackson.databind.jsontype.impl.TypeIdResolverBase;
import com.mimo.common.logic.dto.msg.BaseDispatchMessage.MessageTypeIdResolver;
import com.mimo.common.rpc.proto.DispatchMessageProto;
import com.mimo.common.rpc.proto.DispatchMessageProto.DispatchMessage;
import com.mimo.common.utils.JsonUtils;

import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiModelProperty.AccessMode;

/**
 * 所有分发消息的基类
 * 
 * @author Hongyu
 */

@JsonTypeInfo(use = Id.CUSTOM, include = As.EXISTING_PROPERTY, property = "type", visible = true)
@JsonTypeIdResolver(MessageTypeIdResolver.class)
public abstract class BaseDispatchMessage {

    @ApiModelProperty(value = "消息ID,唯一性标记", required = true, accessMode = AccessMode.READ_ONLY)
    protected String id;

    @NotNull
    @ApiModelProperty(value = "业务上的消息分发类型", required = true, accessMode = AccessMode.READ_ONLY)
    protected DispatchMsgType type;

    @NotEmpty
    @ApiModelProperty(value = "消息发起人", notes = "不论Type类型为何，都用于表示消息发出人", required = true, accessMode = AccessMode.READ_ONLY)
    protected String from;

    @NotEmpty
    @ApiModelProperty(value = "消息投递的目标,一般为用户ID", notes = "如果Type是Peer2Room,则该字段用于存储RoomId", required = true, accessMode = AccessMode.READ_ONLY)
    protected String target;

    @NotEmpty
    @ApiModelProperty(value = "消息内容,由业务上下文决定,可以是plain text也可以是json", required = true, accessMode = AccessMode.READ_ONLY)
    protected String content;

    @NotNull
    @ApiModelProperty(value = "消息创建时的时间戳", required = true, accessMode = AccessMode.READ_ONLY)
    protected long timestamp;

    protected BaseDispatchMessage() {
        super();
        this.timestamp = System.currentTimeMillis();
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public DispatchMsgType getType() {
        return type;
    }

    public void setType(DispatchMsgType type) {
        this.type = type;
    }

    public String getFrom() {
        return from;
    }

    public void setFrom(String from) {
        this.from = from;
    }

    public String getTarget() {
        return target;
    }

    public void setTarget(String target) {
        this.target = target;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public long getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(long timestamp) {
        this.timestamp = timestamp;
    }

    @Override
    public String toString() {
        return JsonUtils.toJsonString(this);
    }

    /**
     * proto的消息格式 ，转换到 业务层的消息格式
     * 
     * @param request
     * @return
     */
    public static BaseDispatchMessage convert(DispatchMessage request) {
        BaseDispatchMessage msg = null;
        if (request.hasP2P()) {
            msg = new P2PMessage();
            msg.setId(request.getId());
            msg.setFrom(request.getFrom());
            msg.setTarget(request.getTarget());
            msg.setContent(request.getContent());
            msg.setTimestamp(request.getTimestamp());
        } else if (request.hasP2R()) {
            msg = new Peer2RoomMessage();
            msg.setId(request.getId());
            msg.setFrom(request.getFrom());
            msg.setTarget(request.getTarget());
            msg.setContent(request.getContent());
            msg.setTimestamp(request.getTimestamp());
        } else if (request.hasR2P()) {
            msg = new Room2PeerMessage();
            msg.setId(request.getId());
            msg.setFrom(request.getFrom());
            msg.setTarget(request.getTarget());
            msg.setContent(request.getContent());
            msg.setTimestamp(request.getTimestamp());
            ((Room2PeerMessage) msg).setRoomId(request.getR2P().getRoomId());
        }
        return msg;
    }

    /**
     * 业务消息格式，转为 proto 格式
     * 
     * @param msg
     * @return
     */
    public static DispatchMessageProto.DispatchMessage convert(BaseDispatchMessage msg) {
        DispatchMessageProto.DispatchMessage.Builder builder = DispatchMessage.newBuilder().setId(msg.getId())
                .setFrom(msg.getFrom()).setTarget(msg.getTarget()).setContent(msg.getContent())
                .setTimestamp(msg.getTimestamp());
        if (msg instanceof P2PMessage) {
            builder.setP2P(
                    DispatchMessageProto.P2PMessage.newBuilder().setType(DispatchMessageProto.DispatchMsgType.P2P));
        } else if (msg instanceof Room2PeerMessage) {
            builder.setR2P(DispatchMessageProto.Room2PeerMessage.newBuilder()
                    .setType(DispatchMessageProto.DispatchMsgType.Room2Peer)
                    .setRoomId(((Room2PeerMessage) msg).getRoomId()));
        } else if (msg instanceof Peer2RoomMessage) {
            builder.setP2R(DispatchMessageProto.Peer2RoomMessage.newBuilder()
                    .setType(DispatchMessageProto.DispatchMsgType.Peer2Room));
        } else {
            throw new IllegalArgumentException(String.format("不支持的操作类型:%s", msg));
        }
        return builder.build();
    }

    /**
     * 自定义一个组件解析器，以便于存储和解析 JSON的动态反序列化
     * 
     * @author Hongyu
     */
    public static class MessageTypeIdResolver extends TypeIdResolverBase {

        @Override
        public String idFromValue(Object value) {
            return BaseDispatchMessage.class.cast(value).getType().name();
        }

        @Override
        public String idFromValueAndType(Object value, Class<?> suggestedType) {
            return BaseDispatchMessage.class.cast(value).getType().name();
        }

        @Override
        public Id getMechanism() {
            throw new UnsupportedOperationException();
        }

        @Override
        public JavaType typeFromId(DatabindContext context, String id) {
            return context.constructType(DispatchMsgType.valueOf(id).getClz());
        }
    }
}
